package wsi.match;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.rygh.core.model.common.StringSet;
import org.rygh.semantic.func.RdfPathFunctionalityController;
import org.rygh.semantic.gui.swing.input.Input;
import org.rygh.semantic.model.ex.PredicatePath;

import wsi.dataguide.tree.WSXmlLeaf;
import wsi.script.AppConfig;
import wsi.subgraph.tree.EntityBranch;
import wsi.subgraph.tree.EntityLeaf;
import wsi.subgraph.tree.EntityTree;

public class SubtreeMatch {
	private final static Set<String> __PRINT_SET = new HashSet<>();
	static {
		String[] ss = {};

		for (String s : ss)
			__PRINT_SET.add(s);
	}

	// if subroot == null, then it is a supnode.
	private final Input input;
	private final WSXmlLeaf subroot;
	private final EntityLeaf graphRoot;
	private final List<MatchPair> matchPairs = new ArrayList<>();
	private final List<MatchPair> graftPairs = new ArrayList<>();

	// private final Map<EntityLeaf, Double> resolveProbMap = new HashMap<>();

	private XmlFunctionalityController xmlFunctionalityController;
	private EntityLeaf centerNode;
	private EntityTree entityTree;

	private int countMatchedLeaves = 0;

	private double sumCompInvFunc = 0;
	private double sumProdCompInvFuncAndInvFunc = 0;
	private double probPositiveValidMatch = 0;
	private double probNegativeValidMatch = 0;

	public SubtreeMatch(Input input, WSXmlLeaf subroot, EntityLeaf graphRoot) {
		this.input = input;
		this.subroot = subroot;
		this.graphRoot = graphRoot;

		assert graphRoot != null;
	}

	public void add(MatchPair mp) {
		matchPairs.add(mp);
	}

	public void compute() {
		if (centerNode == null)
			return;

		reset();

		// if (resolveProbMap.containsKey(centerNode))
		// probPositiveValidMatch = resolveProbMap.get(centerNode);
		// else {
		String center = centerNode.getValue().getValue().getValue();

		boolean print = false;

		if (AppConfig.__DEBUG) {
			print = __PRINT_SET.contains(center);

			if (print) {
				System.out.println("\ncenter node "
						+ centerNode.getValue().getValue());
				System.out.println("subroot " + subroot.getXpath());
			}
		}

		Set<WSXmlLeaf> leafSet = new HashSet<>();

		WSXmlLeaf leaf;
		EntityLeaf node;

		double xml_comp_inv_func, pred_inv_func, conf, prob_sub, prob = 1;

		for (MatchPair i : matchPairs) {
			leaf = i.getXmlLeaf();
			node = i.getEntityLeaf();

			assert leaf != null;
			assert node.getValue().getValue().isLiteral();

			xml_comp_inv_func = xmlFunctionalityController
					.getCompleteInverseFunctionality(leaf.getXpath());
			pred_inv_func = getInverseFunctionality(node);
			conf = i.getConfidence();

			assert xml_comp_inv_func >= 0 && xml_comp_inv_func <= 1 : xml_comp_inv_func
					+ leaf.getXpath().getValue();
			assert pred_inv_func >= 0 && pred_inv_func <= 1 : node.getValue()
					.getPredicate();
			assert conf >= 0 && conf <= 1 : node.getValue().getValue();

			if (!leafSet.contains(leaf)) {
				leafSet.add(leaf);
				sumCompInvFunc += xml_comp_inv_func;
			}

			prob_sub = xml_comp_inv_func * pred_inv_func * conf;
			prob *= (1 - prob_sub);

			sumProdCompInvFuncAndInvFunc += prob_sub;

			if (AppConfig.__DEBUG) {
				if (print) {
					System.out.printf("\tconfidence %.5f \n", conf);
					System.out.printf("\txml_comp_inv_func %.5f, (%s) %s\n",
							xml_comp_inv_func, leaf.getValue().getValue(),
							leaf.getXpath());
					System.out
							.printf("\t    pred_inv_func %.5f, (%s) %s\n",
									pred_inv_func, node.getValue().getValue()
											.getValue(), node.getValue()
											.getPredicate());
					System.out
							.printf("  -> prob_sub, %.5f, prob *= (1 - prob_sub), %.5f\n\n",
									prob_sub, prob);
				}
			}
		}

		// System.out.println("=====");

		probPositiveValidMatch = 1 - prob;
		countMatchedLeaves = leafSet.size();

		if (print) {
			System.out.printf("  --> probPositiveValidMatch %.5f\n\n",
					probPositiveValidMatch);

			// if (centerNode.isRoot())
			// System.out.printf("COMPUTE PROB %.3f center %s subroot %s\n",
			// probPositiveValidMatch, centerNode.getValue().getValue(),
			// subroot.getXpath());
		}

		assert probPositiveValidMatch >= 0 && probPositiveValidMatch <= 1 : probPositiveValidMatch;

		// System.out.printf("storing prob. %s, %s\n", centerNode.getValue()
		// .getValue(), subroot.getXpath());

		// resolveProbMap.put(centerNode, probPositiveValidMatch);

		// computeNegativeEffect();
		// }
	}

	@Deprecated
	void computeNegativeEffect() {
		if (subroot == null) {
			probNegativeValidMatch = Double.NaN;
			return;
		}

		List<WSXmlLeaf> leaves = subroot.getAllLeaves();
		List<EntityLeaf> nodes = graphRoot.getAllNodes();

		double l_cfunc, n_func, p2, prob2 = 1;

		for (WSXmlLeaf l : leaves) {
			for (EntityLeaf n : nodes) {
				if (!n.getValue().getValue().isLiteral())
					continue;

				if (contains(l, n))
					continue;

				l_cfunc = xmlFunctionalityController.getCompleteFunctionality(l
						.getXpath());
				n_func = getFunctionality(n);

				/*
				 * there are cases l_cfunc == NaN. Consider this case, in one
				 * XML document a tag has value <tag><name>value</name></tag>.
				 * But in other XML document with the same schema, this tag is
				 * empty <tag/>. Here tag is turned from a non-leaf to a leaf
				 * node. When we compute functionality, non-leaf is not computed
				 * (as in data guide, tag is non-leaf). Thus when we try to find
				 * functionality for tag, it will fail.
				 */
				if (l_cfunc == Double.NaN)
					continue;

				// System.out.printf("NEG %s %.5f\n", l.getXpath(), l_cfunc);
				// System.out.printf("NEG %s %s %.5f\n",
				// n.getObject().getEntity()
				// .getValue(), n.getObject().getPredicate(), n_func);

				if (l_cfunc == 1)
					l_cfunc = 0.90;

				p2 = l_cfunc * n_func;
				prob2 *= (1 - p2);

			}
		}

		probNegativeValidMatch = prob2;
	}

	public boolean contains(WSXmlLeaf l, EntityLeaf n) {
		for (MatchPair i : matchPairs)
			if (i.valueEquals(l, n))
				return true;

		return false;
	}

	public boolean containsMatch(String value) {
		StringSet ss = new StringSet(value);

		for (MatchPair i : matchPairs) {
			String matched_val = i.getXmlLeaf().getValue().getValue();
			StringSet _ss = new StringSet(matched_val);

			if (ss.equals(_ss))
				return true;
		}

		return false;
	}

	public double getAvgCompInvFunc() {
		return sumCompInvFunc / countMatchedLeaves;
	}

	public double getAvgProdCompInvFuncAndInvFunc() {
		return sumProdCompInvFuncAndInvFunc / matchPairs.size();
	}

	public int getCountMatchedLeaves() {
		return countMatchedLeaves;
	}

	private double getFunctionality(EntityLeaf node) {
		// the concrete root
		assert node.getParent() != null;
		assert centerNode != null;

		// if (node.getParent().getValue() == null)
		// return 1;

		// return getRdfFunctionality(node).getFunctionality();

		// get predicate path

		EntityBranch branch = entityTree.link(centerNode, node);
		PredicatePath path = branch.getPredicatePath();

		return RdfPathFunctionalityController.shared().getFunctionality(path);
	}

	public List<MatchPair> getGraftPairs() {
		return graftPairs;
	}

	public EntityLeaf getGraphRoot() {
		return graphRoot;
	}

	public Input getInput() {
		return input;
	}

	/**
	 * Here, the formula is f^-1(r), thus we call "inverse functionality of r".
	 * But, note the case where r itself is an inverse relation, then the actual
	 * value is the normal functionality of normal r.
	 * 
	 * @param node
	 * @return
	 */
	private double getInverseFunctionality(EntityLeaf node) {
		// the concrete root
		assert node.getParent() != null;
		assert centerNode != null;

		// if (node.getParent().getValue() == null)
		// return 1;
		//
		// return getRdfFunctionality(node).getInverseFunctionality();

		// get predicate path

		EntityBranch branch = entityTree.link(centerNode, node);
		PredicatePath path = branch.getPredicatePath();
		RdfPathFunctionalityController pathCtrl = RdfPathFunctionalityController
				.shared();

		if (path.size() > 1 && !pathCtrl.contains(path)) {
			// System.out.println(entityTree.toString());

			// System.out.printf(
			// "linking from %s to %s \nBranch: \n%s \nPath: %s\n",
			// centerNode.getValue().getValue().getValue(), node
			// .getValue().getValue().getValue(), branch, path);
		}

		return pathCtrl.getInverseFunctionality(path);
	}

	public List<MatchPair> getMatchPairs() {
		return matchPairs;
	}

	public int getNumberOfDistinctMatch() {
		Set<StringSet> set = new HashSet<>();

		for (MatchPair p : matchPairs) {
			StringSet ss = new StringSet(p.getXmlLeaf().getValue().getValue());
			assert !ss.isEmpty();

			set.add(ss);
		}

		return set.size();
	}

	public double getProbNegativeValidMatch() {
		return probNegativeValidMatch;
	}

	public double getProbPositiveValidMatch() {
		return probPositiveValidMatch;
	}

	public WSXmlLeaf getSubroot() {
		return subroot;
	}

	// public double getResolutionProbability(EntityLeaf node) {
	// assert resolveProbMap.containsKey(node) : node.getValue().getValue()
	// + ", " + subroot.getXpath();
	// return resolveProbMap.get(node);
	// }

	public double getSumCompInvFunc() {
		return sumCompInvFunc;
	}

	public double getSumProdCompInvFuncAndInvFunc() {
		return sumProdCompInvFuncAndInvFunc;
	}

	public XmlFunctionalityController getXmlFunctionalityController() {
		return xmlFunctionalityController;
	}

	public void graftPairs(SubtreeMatch sm) {
		graftPairs.addAll(sm.matchPairs);
	}

	public boolean isEmpty() {
		return matchPairs.isEmpty();
	}

	private void reset() {
		countMatchedLeaves = 0;
		sumCompInvFunc = 0;
		sumProdCompInvFuncAndInvFunc = 0;
		probPositiveValidMatch = 0;
		probNegativeValidMatch = 0;
	}

	public void setCenterNode(EntityLeaf centerNode) {
		this.centerNode = centerNode;
	}

	public void setEntityTree(EntityTree entityTree) {
		this.entityTree = entityTree;
	}

	public void setXmlFunctionalityController(
			XmlFunctionalityController xmlFunctionalityController) {
		this.xmlFunctionalityController = xmlFunctionalityController;
	}

	public int size() {
		return matchPairs.size();
	}

}
